package mr.go.set.tests.performance;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import mr.go.set.AggregatedSet;
import mr.go.set.samples.Counter;

public final class Test {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			for (int i = 1; i <= MAX_THREADS; i++) {
				System.out.println("Insertion (" + i + " thread(s))");
				float ourResult = 0f;
				float chmResult = 0f;
				for (int j = 0; j < TESTS; j++) {
					Test test = new Test(i);
					float[] results = test.testSimpleInsertion();
					ourResult += results[0];
					chmResult += results[1];
					test.finish();
				}
				ourResult /= TESTS;
				chmResult /= TESTS;
				System.out.println("AggregatedSet: " + ourResult + " us");
				System.out.println("ConcurrntHashMap: " + chmResult + " us");
				ourResult = 0f;
				chmResult = 0f;
				for (int j = 0; j < TESTS; j++) {
					Test test = new Test(i);
					float[] results = test.testInsertionsAndRemovals();
					ourResult += results[0];
					chmResult += results[1];
					test.finish();
				}
				ourResult /= TESTS;
				chmResult /= TESTS;
				System.out.println("Insertion and deletion (" + i
									+ " thread(s))");
				System.out.println("AggregatedSet: " + ourResult + " us");
				System.out.println("ConcurrntHashMap: " + chmResult + " us");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}

	}

	private final ConcurrentHashMap<Double, Integer>	concurrentHashMap;

	private final ExecutorService						executor;

	private final int									nThreads;

	private final AggregatedSet<Double, Integer>		ourSet	= new AggregatedSet<Double, Integer>(
																		new Counter());

	private final double[]								randoms	= new double[INSERTIONS_COUNT];

	public Test(
			int nThreads) {
		concurrentHashMap = new ConcurrentHashMap<Double, Integer>(
				16,
				0.75f,
				nThreads);
		executor = Executors.newFixedThreadPool(nThreads);
		this.nThreads = nThreads;
		for (int i = 0; i < INSERTIONS_COUNT; i++)
			randoms[i] = Math.random();
	}

	public void finish() {
		executor.shutdown();
	}

	public float[] testInsertionsAndRemovals()
			throws InterruptedException,
			ExecutionException {
		Callable<Long>[] tests = new Callable[nThreads + 1];
		for (int i = 0; i < nThreads; i++) {
			tests[i] = new SimpleInsertionTestSet();
		}
		tests[nThreads] = new SetThief();
		List<Future<Long>> results = executor.invokeAll(Arrays.asList(tests));
		float[] times = new float[2];
		for (Future<Long> result : results) {
			Long ourResult = result.get();
			times[0] += ourResult;
		}
		times[0] /= (nThreads + 1);
		for (int i = 0; i < nThreads; i++) {
			tests[i] = new SimpleInsertionTestConcurrentMap();
		}
		tests[nThreads] = new MapThief();
		results = executor.invokeAll(Arrays.asList(tests));
		for (Future<Long> result : results) {
			Long mapResult = result.get();
			times[1] += mapResult;
		}
		times[1] /= (nThreads + 1);
		return times;
	}

	public float[] testSimpleInsertion()
			throws InterruptedException,
			ExecutionException {
		SimpleInsertionTestSet[] testsSet = new SimpleInsertionTestSet[nThreads];
		for (int i = 0; i < nThreads; i++) {
			testsSet[i] = new SimpleInsertionTestSet();
		}
		List<Future<Long>> results = executor
				.invokeAll(Arrays.asList(testsSet));
		float[] times = new float[2];
		for (Future<Long> result : results) {
			Long ourResult = result.get();
			times[0] += ourResult;
		}
		times[0] /= nThreads;
		SimpleInsertionTestConcurrentMap[] testsMap = new SimpleInsertionTestConcurrentMap[nThreads];
		for (int i = 0; i < nThreads; i++) {
			testsMap[i] = new SimpleInsertionTestConcurrentMap();
		}
		results = executor.invokeAll(Arrays.asList(testsMap));
		for (Future<Long> result : results) {
			Long mapResult = result.get();
			times[1] += mapResult;
		}
		times[1] /= nThreads;
		return times;
	}

	private class MapThief implements Callable<Long> {

		@Override
		public Long call() throws Exception {
			while (concurrentHashMap.size() < DELETIONS_COUNT)
				Thread.sleep(1);
			List<Double> elementsSoFar = new LinkedList<Double>();
			int size = 0;
			for (Double d : concurrentHashMap.keySet()) {
				elementsSoFar.add(d);
				if (size++ == DELETIONS_COUNT)
					break;
			}
			long chmTimer = 0;
			for (Double d : elementsSoFar) {
				long start = System.nanoTime();
				concurrentHashMap.remove(d);
				long finish = System.nanoTime();
				chmTimer += (finish - start);
			}
			return chmTimer / (DELETIONS_COUNT * 1000);
		}

	}

	private class SetThief implements Callable<Long> {

		@Override
		public Long call() throws Exception {
			while (ourSet.size() < DELETIONS_COUNT)
				Thread.sleep(1);
			List<Double> elementsSoFar = new LinkedList<Double>();
			int size = 0;
			for (Double d : ourSet) {
				elementsSoFar.add(d);
				if (size++ == DELETIONS_COUNT)
					break;
			}
			long ourTimer = 0;
			for (Double d : elementsSoFar) {
				long start = System.nanoTime();
				ourSet.remove(d);
				long finish = System.nanoTime();
				ourTimer += (finish - start);
			}
			return ourTimer / (DELETIONS_COUNT * 1000);
		}

	}

	private class SimpleInsertionTestConcurrentMap implements Callable<Long> {

		@Override
		public Long call() throws Exception {
			long chmTimer = 0;
			for (int j = 0; j < randoms.length; j++) {
				double random = randoms[j];
				long start = System.nanoTime();
				concurrentHashMap.put(random, 0);
				long finish = System.nanoTime();
				chmTimer += (finish - start);
			}
			return chmTimer / (INSERTIONS_COUNT * 1000);
		}

	}

	private class SimpleInsertionTestSet implements Callable<Long> {

		@Override
		public Long call() throws Exception {
			long ourTimer = 0;
			for (int j = 0; j < randoms.length; j++) {
				double random = randoms[j];
				long start = System.nanoTime();
				ourSet.add(random);
				long finish = System.nanoTime();
				ourTimer += (finish - start);

			}
			return ourTimer / (INSERTIONS_COUNT * 1000);
		}

	}

	private static final int	DELETIONS_COUNT		= 300;

	private static final int	INSERTIONS_COUNT	= 10000;

	private static final int	MAX_THREADS			= 16;

	private static final int	TESTS				= 20;

}
